package logger

import (
	"context"
	"fmt"
	"go.uber.org/atomic"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"os"
	"path"
	"strings"
	"time"
)

// A Level is a logging priority. Higher levels are more important.
type Level int8

const (
	// DebugLevel logs are typically voluminous, and are usually disabled in
	// production.
	DebugLevel Level = iota - 1
	// InfoLevel is the default logging priority.
	InfoLevel
	// WarnLevel logs are more important than Info, but don't need individual
	// human review.
	WarnLevel
	// ErrorLevel logs are high-priority. If an application is running smoothly,
	// it shouldn't generate any error-level logs.
	ErrorLevel
	// DPanicLevel logs are particularly important errors. In development the
	// logger panics after writing the message.
	DPanicLevel
	// PanicLevel logs a message, then panics.
	PanicLevel
	// FatalLevel logs a message, then calls os.Exit(1).
	FatalLevel

	_minLevel = DebugLevel
	_maxLevel = FatalLevel
)

var sugarLogger *zap.SugaredLogger
var logLevel = zap.NewAtomicLevel()
var logWriter zapcore.WriteSyncer
var logConfig LogConfig

type FileText struct {
	running atomic.Bool
}

func GetSugarLogger() *zap.SugaredLogger {
	return sugarLogger
}

func GetLogWriter() zapcore.WriteSyncer {
	return logWriter
}

// Sync flushes any buffered log entries.
func Sync() error {
	return sugarLogger.Sync()
}

func SetLevel(level Level) {
	logLevel.SetLevel(zapcore.Level(level))
}

func InitLogger(config LogConfig) {
	logConfig = config
	sugarLogger = getLogger(config)
}

func getLogger(config LogConfig) *zap.SugaredLogger {
	encoder := getEncoder()

	var cors []zapcore.Core
	if config.Console { // add console
		consoleWriter := zapcore.Lock(os.Stdout)
		cors = append(cors, zapcore.NewCore(encoder, consoleWriter, logLevel))
	}
	logWriter = getLogWriter(config)
	cors = append(cors, zapcore.NewCore(encoder, logWriter, logLevel))
	fileDir, fileName := path.Split(config.Filename)
	errFileName := fileDir + strings.Split(fileName, path.Ext(fileName))[0] + ".error" + path.Ext(fileName)
	errWriter := getLogWriter(LogConfig{Filename: errFileName, LogRotate: config.LogRotate, MaxSize: config.MaxSize,
		MaxDays: config.MaxDays, MaxBackups: config.MaxBackups, Console: config.Console})
	cors = append(cors, zapcore.NewCore(encoder, errWriter, zap.ErrorLevel))
	core := zapcore.NewTee(cors...)
	logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
	zap.RedirectStdLog(logger)
	return logger.Sugar()
}

func NewLogger(fileName string) *zap.SugaredLogger {
	filePath := path.Dir(logConfig.Filename) + "/" + fileName
	return getLogger(LogConfig{
		Filename:   filePath,
		LogRotate:  logConfig.LogRotate,
		MaxSize:    logConfig.MaxSize,
		Console:    logConfig.Console,
		MaxDays:    logConfig.MaxDays,
		MaxBackups: logConfig.MaxBackups,
	})
}

func getEncoder() zapcore.Encoder {
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
		encoder.AppendString(t.Format("2006-01-02 15:04:05"))
	}
	encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
	encoderConfig.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) {
		idx := strings.LastIndex(caller.File, "/")
		encoder.AppendString(fmt.Sprintf("%s:%d", caller.File[idx+1:], caller.Line))
	}
	return zapcore.NewConsoleEncoder(encoderConfig)
}

func getLogWriter(config LogConfig) zapcore.WriteSyncer {
	lumberJackLogger := &lumberjack.Logger{
		Filename:   config.Filename,
		MaxSize:    config.MaxSize,
		MaxBackups: config.MaxBackups,
		MaxAge:     30,
		Compress:   false,
		LocalTime:  true,
	}
	return zapcore.AddSync(lumberJackLogger)
}

type ctxLogKeyType struct{}

var ctxLogKey = ctxLogKeyType{}

func CtxLogger(ctx context.Context) *zap.SugaredLogger {
	if ctxlogger, ok := ctx.Value(ctxLogKey).(*zap.SugaredLogger); ok {
		return ctxlogger
	}
	return sugarLogger
}

func LogsWithConnID(connID int64) *zap.SugaredLogger {
	return sugarLogger.With(zap.Int64("conn", connID))
}
